﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PowerTriggers.ServiceReference;
using System.ServiceModel;
using System.Diagnostics;
using System.ServiceProcess;

namespace PowerTriggers
{
    internal static class WcfClientFactory
    {
        private volatile static PowerTriggersWcfCallbackClient callbackClient;
        private volatile static string defaultServerName;
        private volatile static ServiceController serviceController;
        private static object lockObject = new object();
        private volatile static InstanceContext callbackInstanceContext;

        internal static PowerTriggersWcfClient OpenWcfClient()
        {
            return OpenWcfClient(defaultServerName);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        internal static PowerTriggersWcfClient OpenWcfClient(string serverName)
        {
            PowerTriggersWcfClient serviceClient;

            // there is some sort of intermittent bug with wcf endpoint that means that the first attempt to
            // open a client connection may fail, but a second attempt will succeed
            bool errorLogged = false;
            try
            {
                lock (lockObject)
                {
                    serviceClient = new PowerTriggersWcfClient();
                    try
                    {
                        CustomiseClient<PowerTriggers.ServiceReference.IPowerTriggersWcf>(serviceClient, serverName);
                        serviceClient.Open();
                    }
                    catch (System.Exception ex)
                    {
                        EventLogHelper.LogError("RefreshServiceData() First open clientClient failed", ex);
                        try
                        {
                            ((IDisposable)serviceClient).Dispose();
                        }
                        catch { }
                        serviceClient = null;
                        errorLogged = true;
                        throw;
                    }
                }
            }
            catch (System.Exception ex)
            {
                if (!errorLogged)
                {
                    EventLogHelper.LogError("RefreshServiceData() First Open clientClient failed", ex);
                }

                lock (lockObject)
                {
                    // if failed, then try again, but this time raise an exception if connection fails
                    serviceClient = new PowerTriggersWcfClient();
                    try
                    {
                        CustomiseClient<PowerTriggers.ServiceReference.IPowerTriggersWcf>(serviceClient, serverName);
                        serviceClient.Open();
                    }
                    catch (System.Exception ex2)
                    {
                        EventLogHelper.LogError("RefreshServiceData() Second Open clientClient failed", ex2);
                        try
                        {
                            ((IDisposable)serviceClient).Dispose();
                        }
                        catch { }
                        throw;
                    }
                }
            }

            return serviceClient;
        }

        internal static void OpenCallbackClient(IPowerTriggersWcfCallbackCallback callback)
        {
            try
            {
                lock (lockObject)
                {
                    callbackInstanceContext = new InstanceContext(callback);
                    OpenCallbackClient(callbackInstanceContext);
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("ConnectToService() Open callbackClient failed", ex);
                CloseCallbackClient();
                throw;
            }
        }

        private static void OpenCallbackClient(InstanceContext context)
        {
            try
            {
                EnsureWindowsServiceIsRunning(defaultServerName);

                CloseCallbackClient();

                lock (lockObject)
                {
                    callbackClient = new PowerTriggersWcfCallbackClient(context);
                    CustomiseClient<PowerTriggers.ServiceReference.IPowerTriggersWcfCallback>(callbackClient, defaultServerName);
                    callbackClient.Open();
                    if (callbackClient.InnerChannel != null)
                    {
                        callbackClient.InnerChannel.Faulted += new EventHandler(Channel_Faulted);
                    }
                    if (callbackClient.InnerDuplexChannel != null && callbackClient.InnerDuplexChannel != callbackClient.InnerChannel)
                    {
                        callbackClient.InnerDuplexChannel.Faulted += new EventHandler(Channel_Faulted);
                    }

                    callbackClient.SubscribeHistoryChangedEvent();
                    callbackClient.RequestHistoryCallback();
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("ConnectToService() Open callbackClient failed", ex);
                CloseCallbackClient();
                throw;
            }
        }

        private static void Channel_Faulted(object sender, EventArgs e)
        {
            EventLogHelper.LogWarning("The callback channel has reported itself as faulted");
            try
            {
                CloseCallbackClient();
            }
            catch { }
            try
            {
                if (callbackInstanceContext != null && callbackInstanceContext.State != CommunicationState.Faulted)
                {
                    OpenCallbackClient(callbackInstanceContext);
                }
            }
            catch { }

        }

        internal static void CloseCallbackClient()
        {
            if (callbackClient != null)
            {
                lock (lockObject)
                {
                    try
                    {
                        if (callbackClient.InnerChannel != null)
                        {
                            callbackClient.InnerChannel.Faulted -= new EventHandler(Channel_Faulted);
                        }
                        if (callbackClient.InnerDuplexChannel != null)
                        {
                            callbackClient.InnerDuplexChannel.Faulted -= new EventHandler(Channel_Faulted);
                        }
                    }
                    catch { }

                    try
                    {
                        callbackClient.UnsubscribeHistoryChangedEvent();
                    }
                    catch { }

                    try
                    {
                        callbackClient.Close();
                    }
                    catch { }

                    try
                    {
                        ((IDisposable)callbackClient).Dispose();
                    }
                    catch { }

                    callbackClient = null;
                }
            }
        }

        internal static bool IsCallbackClientClosed
        {
            get
            {
                return callbackClient == null || callbackClient.InnerChannel == null || callbackClient.InnerChannel.State != CommunicationState.Opened;
            }
        }

        private static void CustomiseClient<TChannel>(ClientBase<TChannel> client, string serverName) where TChannel : class
        {
            if (!string.IsNullOrEmpty(serverName))
            {
                // modify the name of the computer to connect to
                EndpointAddressBuilder addressBuilder = new EndpointAddressBuilder(client.Endpoint.Address);
                addressBuilder.Uri = new Uri(client.Endpoint.Address.Uri.ToString().ToLower().Replace("localhost", serverName));
                addressBuilder.Identity = new SpnEndpointIdentity("host/" + serverName);

                client.Endpoint.Address = addressBuilder.ToEndpointAddress();
            }
        }

        internal static string CurrentServerName
        {
            get
            {
                return defaultServerName;
            }
            set
            {
                lock (lockObject)
                {
                    if (string.IsNullOrEmpty(value) != string.IsNullOrEmpty(defaultServerName)
                        || value != defaultServerName)
                    {
                        defaultServerName = value == null ? null : value.Trim().ToUpper();
                        if (defaultServerName != null)
                        {
                            if (defaultServerName.Length == 0 || defaultServerName == "" || defaultServerName == "." || defaultServerName == "locahost")
                            {
                                defaultServerName = null;
                            }
                        }

                        if (serviceController != null)
                        {
                            serviceController.Dispose();
                            serviceController = null;
                        }
                    }
                }
            }
        }

        private static ServiceController GetServiceController()
        {
            if (serviceController == null)
            {
                lock (lockObject)
                {
                    if (defaultServerName == null)
                    {
                        serviceController = new ServiceController(Properties.Settings.Default.ServiceName);
                    }
                    else
                    {
                        serviceController = new ServiceController(Properties.Settings.Default.ServiceName, defaultServerName);
                    }
                }
            }

            return serviceController;
        }

        internal static bool DoesWindowsServiceExist(string serverName)
        {
            bool exists = false;
            try
            {
#if DEBUG
                if (string.IsNullOrEmpty(serverName))
                {
                    // local computer
                    Process[] processes = Process.GetProcessesByName("WcfSvcHost");
                    if (processes.Length > 0)
                    {
                        exists = true;
                        foreach (var process in processes)
                        {
                            process.Dispose();
                        }
                    }
                }
#endif
                if (!exists)
                {
                    lock (lockObject)
                    {
                        ServiceController controller = GetServiceController();
                        controller.Refresh();
                        exists = controller.Status != ServiceControllerStatus.Paused;
                    }
                }
            }
            catch (InvalidOperationException) // fires if the servername is invalid or if the ControllerService cannot find the named service
            {
                exists = false;
            }

            return exists;
        }

        internal static void EnsureWindowsServiceIsRunning(string serverName)
        {
            EnsureWindowsServiceIsRunning(serverName, false);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EnsureServiceIsRunning"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "PowerTriggers.EventLogHelper.LogError(System.String,System.Exception)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "PowerTriggers.EventLogHelper.LogError(System.String)")]
        internal static void EnsureWindowsServiceIsRunning(string serverName, bool longWait)
        {
#if !DEBUG
            try
            {
                lock (lockObject)
                {
                    ServiceController controller = GetServiceController();
                    if (controller.Status == ServiceControllerStatus.Stopped || controller.Status == ServiceControllerStatus.StopPending)
                    {
                        try
                        {
                            controller.Start();
                        }
                        catch { }
                        try
                        {
                            controller.Refresh();
                        }
                        catch { }
                    }
                    if (controller.Status == ServiceControllerStatus.StartPending)
                    {
                        try
                        {
                            TimeSpan timeout = new TimeSpan(0, 0, 10);
                            if (longWait)
                            {
                                timeout = new TimeSpan(0,0,30);
                            }
                            controller.WaitForStatus(ServiceControllerStatus.Running, timeout);
                        }
                        catch { }

                        try
                        {
                            controller.Refresh();
                        }
                        catch { }
                    }
                    if (controller.Status != ServiceControllerStatus.Running && controller.Status != ServiceControllerStatus.StartPending)
                    {
                        string msg = string.Format("Service '{0}' is not running. Reported state is {1}", controller.ServiceName, controller.Status.ToString());
                        EventLogHelper.LogError("EnsureServiceIsRunning Error: " + msg);
                    }
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("EnsureServiceIsRunning Error", ex);
            }
#endif
        }
    }
}
